package org.osomit.sacct.client.impl;

import org.osomit.sacct.connector.socket.iface.SocketConnectorProvider;
import org.osomit.sacct.connector.socket.iface.SocketConnector;
import org.osomit.sacct.io.iface.ClientReaderWriter;
import org.osomit.sacct.handler.iface.ClientServiceHandler;
import org.osomit.sacct.entity.SessionInfo;
import org.osomit.sacct.entity.ApplicationAccount;
import org.osomit.sacct.session.iface.SessionContextHolder;
import org.osomit.sacct.session.SecureRequestContext;
import org.osomit.sacct.session.SessionContext;
import org.osomit.sacct.security.key.exchange.impl.SkipClient;
import org.osomit.sacct.security.utils.SecurityUtil;
import org.osomit.sacct.security.utils.SharedSecurityGenerator;
import org.osomit.sacct.security.otp.iface.OTPToken;
import org.osomit.sacct.security.otp.impl.OTPTokenImpl;
import org.osomit.sacct.security.otp.impl.HOTPAlgorithm;
import org.osomit.sacct.client.iface.AccountClient;
import org.apache.commons.codec.binary.Base64;

import javax.crypto.SecretKey;
import java.util.List;

/**
 * @author Jian Fang (John.Jian.Fang@gmail.com)
 *
 * Date: Jun 29, 2009
 */
public class AccountClientImpl implements AccountClient {
    
    private SkipClient skipClient = new SkipClient();

    private SocketConnectorProvider connectorProvider;

    private ClientReaderWriter readerWriter;

    private ClientServiceHandler serviceHandler;

    private String clientId;

    private SessionContextHolder sessionContextHolder;

    private boolean useOTP = false;

    public boolean isUseOTP() {
        return useOTP;
    }

    public void setUseOTP(boolean useOTP) {
        this.useOTP = useOTP;
    }

    public SessionContextHolder getSessionContextHolder() {
        return sessionContextHolder;
    }

    public void setSessionContextHolder(SessionContextHolder sessionContextHolder) {
        this.sessionContextHolder = sessionContextHolder;
    }

    public String getClientId() {
        return clientId;
    }

    public void setClientId(String clientId) {
        this.clientId = clientId;
    }

    public SocketConnectorProvider getConnectorProvider() {
        return connectorProvider;
    }

    public void setConnectorProvider(SocketConnectorProvider connectorProvider) {
        this.connectorProvider = connectorProvider;
    }

    public ClientServiceHandler getServiceHandler() {
        return serviceHandler;
    }

    public void setServiceHandler(ClientServiceHandler serviceHandler) {
        this.serviceHandler = serviceHandler;
    }

    public ClientReaderWriter getReaderWriter() {
        return readerWriter;
    }

    public void setReaderWriter(ClientReaderWriter readerWriter) {
        this.readerWriter = readerWriter;
    }

    protected SecureRequestContext getDefaultRequestContext(){
        SecureRequestContext request = new SecureRequestContext();
        request.setClientId(this.clientId);
        request.setLocale("us_EN");

        return request;
    }

    public void connect(String serverHost, int port){
        SocketConnector connector = this.connectorProvider.getConnector(serverHost, port);
        connector.setClientReaderWriter(this.readerWriter);
        this.serviceHandler.setConnector(connector);
        this.serviceHandler.connect();
        this.doHandshake();
    }

    public String sayHello(){
        SecureRequestContext context = this.getDefaultRequestContext();
        
        return this.serviceHandler.hello(context);
    }

    protected void doHandshake() {
        SecureRequestContext context = this.getDefaultRequestContext();
        byte[] publicKeyEnc = this.skipClient.generatePublicKey();
        SessionInfo sessionInfo = this.serviceHandler.handshake(context, SecurityUtil.base64Encode(publicKeyEnc));
//        SecretKey sessionKey = this.skipClient.generateDESKey(Base64.decodeBase64(sessionInfo.getServerPublicKey().getBytes()));
         SecretKey sessionKey = this.skipClient.generateSecretKey(Base64.decodeBase64(sessionInfo.getServerPublicKey().getBytes()));
        SessionContext sc = new SessionContext();
        sc.setSessionId(sessionInfo.getSessionId());
        sc.setSessionKey(sessionKey);
        if(this.useOTP){
            OTPToken otptk = this.generateOTPToken(sessionInfo.getSessionId(), sessionKey.getEncoded());
            sc.setOtpToken(otptk);
        }
        
        this.sessionContextHolder.setSessionContext(sc);
    }

    //Use hard-coded OTP token parameters for the time being because too many configurations will
    //make people confused and they do not really need to know how to adjust the parameters for
    //the One Time Password Token
    protected OTPToken generateOTPToken(String sn, byte[] sharedSecret){

        return new OTPTokenImpl(sn, sharedSecret, 1000, this.getBais(sn), 9, true, 20, 100, new HOTPAlgorithm());
    }

    protected long getBais(String sn){
        String[] parts = sn.split("-");

        return Long.parseLong(parts[1]);
    }

    public ApplicationAccount fetchAccount(String accountName) {
        SecureRequestContext context = this.getDefaultRequestContext();
        SessionContext sc = this.sessionContextHolder.getSessionContext();
        context.setSessionContext(sc);
        if(this.useOTP && sc.getOtpToken() != null){
            context.setRequestId(sc.getOtpToken().generateOTP());
        }

        return this.serviceHandler.fetchAccount(context, accountName);
    }

    public List<ApplicationAccount> fetchAllAccounts(){
        SecureRequestContext context = this.getDefaultRequestContext();
        SessionContext sc = this.sessionContextHolder.getSessionContext();
        context.setSessionContext(sc);
        if(this.useOTP && sc.getOtpToken() != null){
            context.setRequestId(sc.getOtpToken().generateOTP());
        }

        return this.serviceHandler.fetchAllAccounts(context);
    }

    public void disconnect() {
        SecureRequestContext context = this.getDefaultRequestContext();
        SessionContext sc = this.sessionContextHolder.getSessionContext();
        if (sc != null) {
            context.setSessionContext(sc);
            if (this.useOTP && sc.getOtpToken() != null) {
                context.setRequestId(sc.getOtpToken().generateOTP());
            }
            this.serviceHandler.closeSession(context, SecurityUtil.base64Encode(SharedSecurityGenerator.generateSharedSecret(64)));
        }

        this.serviceHandler.disconnect();
    }
}
